package com.ops.uitl.config;

import com.ops.uitl.common.Common;
import com.ops.uitl.data.DataMap;
import com.ops.uitl.data.DatasetList;
import com.ops.uitl.data.IData;
import com.ops.uitl.data.IDataset;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.jdom.xpath.XPath;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.StringReader;
import java.util.Iterator;
import java.util.List;

public class XMLConfig implements IConfig {
	public static final String XML_FORMAT_CONTENT = "CONTENT";
	public static final String XML_FORMAT_ATTRIBUTE = "ATTRIBUTE";

	private Document document;
	private Element root;
	private String filepath;

	/**
	 * construct function
	 * @param file
	 * @throws Exception
	 */
	public XMLConfig(String file) throws Exception {
		this(Common.getInstance().getClassResourceStream(file));
	}

	/**
	 * construct function
	 * @param in
	 * @throws Exception
	 */
	public XMLConfig(InputStream in) throws Exception {
		SAXBuilder builder = new SAXBuilder();
		builder.setEntityResolver(new NoOpEntityResolver());
		document = builder.build(in);
		in.close();
		root = document.getRootElement();
	}

	/**
	 * construct function
	 * @param file
	 * @throws Exception
	 */
	public XMLConfig(File file) throws Exception {
		filepath = file.getPath();
		SAXBuilder builder = new SAXBuilder();
		builder.setEntityResolver(new NoOpEntityResolver());
		document = builder.build(file);
		root = document.getRootElement();
	}

	/**
	 * get root element
	 * @return String
	 * @throws Exception
	 */
	public Element getRootElement() throws Exception {
		return root;
	}

	/**
	 * get child
	 * for example:subsys/sale/addr
	 * @param prop
	 * @return String
	 * @throws Exception
	 */
	public Element getChild(String prop) throws Exception {
		Element element = root;
		if (prop == null) return element;

		String[] nodes = prop.split("/");
		for (int i=0; i<nodes.length; i++) {
			element = element.getChild(nodes[i]);
		}

		return element;
	}

	/**
	 * get property
	 * for example:subsys\sale\addr
	 * @param prop
	 * @return String
	 * @throws Exception
	 */
	public String getProperty(String prop) throws Exception {
		prop = parserPropStr(prop);
		Element element = root;

		String[] nodes = prop.split("/");
		for (int i=0; i<nodes.length; i++) {
			if (i == nodes.length - 1) break;
			element = element.getChild(nodes[i]);
		}

		return element.getAttributeValue(nodes[nodes.length - 1]);
	}

	/**
	 * set property
	 * @param prop
	 * @param value
	 * @throws Exception
	 */
	public void setProperty(String prop, String value) throws Exception {
		prop = parserPropStr(prop);
		Element element = root;

		String[] nodes = prop.split("/");
		for (int i=0; i<nodes.length; i++) {
			if (i == nodes.length - 1) break;
			element = element.getChild(nodes[i]);
		}

		element.setAttribute(nodes[nodes.length - 1], value);
	}

	/**
	 * set text
	 * @param textName,format: /web-app/display-name
	 * @param oldTextValue
	 * @param newTextValue
	 * @throws Exception
	 */
	public void setText(String textName, String oldTextValue, String newTextValue) throws Exception {
		Element element = root;
		if (textName.startsWith("/")) {
			textName = textName.substring(1);
		}

		modifyText(element, textName, oldTextValue, newTextValue);
	}

	/**
	 * set string
	 * @parma xmlstr
	 * @throws Exception
	 */
	public void setString(String xmlstr) throws Exception {
		SAXBuilder builder = new SAXBuilder();
		document = builder.build(new StringReader(xmlstr));
	}

	/**
	 * get properties
	 * for example:subsys\*\addr or database\eparchy
	 * @param prop
	 * @return IData
	 * @throws Exception
	 */
	public IData getProperties(String prop) throws Exception {
		IData properties = new DataMap();

		if (prop.indexOf("*") == -1) {
			Element element = getChild(prop);
			List attrs = element.getAttributes();
			for (int i=0; i<attrs.size(); i++) {
				Attribute attr = (Attribute) attrs.get(i);
				properties.put(attr.getName(), attr.getValue());
			}
		} else {
			Element element = root;
			String[] nodes = prop.split("/");
			for (int i=0; i<nodes.length; i++) {
				if ("*".equals(nodes[i])) {
					List children = element.getChildren();
					for (int j=0; j<children.size(); j++) {
						Element child = (Element) children.get(j);
						String childName = child.getName();
						String childValue = child.getAttributeValue(nodes[i + 1]);
						properties.put(childName, childValue);
					}
					break;
				} else {
					element = element.getChild(nodes[i]);
				}
			}
		}

		return properties;
	}

	/**
	 * get dataset
	 * @return IDataset
	 * @throws Exception
	 */
	public IDataset getDataset() throws Exception {
		return getDataset(null, 0, XML_FORMAT_CONTENT);
	}

	/**
	 * get dataset
	 * @param prop
	 * @return IDataset
	 * @throws Exception
	 */
	public IDataset getDataset(String prop) throws Exception {
		return getDataset(prop, 0, XML_FORMAT_CONTENT);
	}

	/**
	 * get dataset
	 * @param prop
	 * @param format
	 * @return IDataset
	 * @throws Exception
	 */
	public IDataset getDataset(String prop, String format) throws Exception {
		return getDataset(prop, 0, format);
	}

	/**
	 * 用XPath格式查找元素
	 * @param path
	 * @return IDataset
	 * @throws Exception
	 */
	public IDataset getElements(String path) throws Exception {
		IDataset dataset = new DatasetList();
		List list = XPath.selectNodes(root, path);

		if (list != null) {
			for (int i = 0; i < list.size(); i++) {
				IData data = new DataMap();
				Element element = (Element) list.get(i);

				List attrs = element.getAttributes();
				for (int j = 0; j < attrs.size(); j++) {
					Attribute attr = (Attribute) attrs.get(j);
					data.put(attr.getName(), attr.getValue());
				}
				data.put("_type", element.getName());
				dataset.add(data);
			}
		}

		return dataset;
	}

	/**
	 * get dataset
	 * @param prop
	 * @param index
	 * @param format
	 * @return IDataset
	 * @throws Exception
	 */
	public IDataset getDataset(String prop, int index, String format) throws Exception {
		IDataset dataset = new DatasetList();

		Element element = getChild(prop);
		List rows = element.getChildren();

		for (int i=0; i<index; i++) {
			rows = ((Element) rows.get(i)).getChildren();
		}

		for (int i=0; i<rows.size(); i++) {
			IData data = new DataMap();

			Element row = (Element) rows.get(i);

			if (XML_FORMAT_CONTENT.equals(format)) {
				List cells = row.getChildren();
				for (int j=0; j<cells.size(); j++) {
					Element cell = (Element) cells.get(j);
					data.put(cell.getName(), cell.getValue());
				}
			}

			if (XML_FORMAT_ATTRIBUTE.equals(format)) {
				List cells = row.getAttributes();
				for (int j=0; j<cells.size(); j++) {
					Attribute cell = (Attribute) cells.get(j);
					data.put(cell.getName(), cell.getValue());
				}
			}

			dataset.add(data);
		}

		return dataset;
	}

	/**
	 * save
	 * @throws Exception
	 */
	public void save() throws Exception {
		Format format = Format.getCompactFormat();
		format.setEncoding("UTF-8");
		format.setIndent("    ");

		XMLOutputter outputer = new XMLOutputter(format);
		FileOutputStream out = new FileOutputStream(filepath);
		outputer.output(document, out);
		out.close();
	}

	/**
	 * to string
	 * @return String
	 */
	public String toString() {
		Format format = Format.getCompactFormat();
		format.setEncoding("UTF-8");
		format.setIndent("    ");

		XMLOutputter out = new XMLOutputter(format);
		return out.outputString(document);
	}

	protected String parserPropStr(String prop) throws Exception {
		if (prop.startsWith("/")) {
			prop = prop.substring(1);
			return prop.indexOf("/") == -1 ? "" : prop.substring(prop.indexOf("/") + 1);
		}
		return prop;
	}

	protected void modifyText(Element root, String textName,
							  String oldTextValue, String newTextValue) throws Exception {
		if (textName.indexOf("/") < 0) {
			if (oldTextValue.equals(root.getText()))
				root.setText(newTextValue);
			return;
		}
		String subTextName = textName.substring(textName.indexOf("/") + 1);
		String newTextName = subTextName;
		if (subTextName.indexOf("/") > 0) {
			newTextName = subTextName.substring(0, subTextName.indexOf("/"));
		}

		Iterator newEleIter = root.getChildren(newTextName).iterator();
		while (newEleIter.hasNext()) {
			Element newRoot = (Element) newEleIter.next();
			modifyText(newRoot, subTextName, oldTextValue, newTextValue);
		}
	}

	protected class NoOpEntityResolver implements EntityResolver {
		public InputSource resolveEntity(String publicId, String systemId) {
			return new InputSource(new String(""));
		}
	}
}
